package org.hit.burkun.obo.model;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;

import org.hit.burkun.obo.model.Relationship.RELATION;
import org.obolibrary.oboformat.model.Clause;
import org.obolibrary.oboformat.model.Frame;
import org.obolibrary.oboformat.model.OBODoc;
import org.obolibrary.oboformat.model.Xref;
import org.obolibrary.oboformat.parser.OBOFormatConstants.OboFormatTag;
import org.obolibrary.oboformat.parser.OBOFormatParser;

public class OboParser {
	private String path;
	private OBOFormatParser parser = new OBOFormatParser();

	public OboParser(String fullPath) {
		this.path = fullPath;
	}

	public void parse() throws IOException {
		OBODoc doc = parser.parse(path);
		// //testing --begin
		// Frame f = doc.getTermFrame("DOID:0050702");
		// Clause c = f.getClause(OboFormatTag.TAG_DEF);
		// System.out.println(c.getTag());
		// System.out.println(c.getValue());
		// System.out.println(c.getValue2());
		// //testing --end
		initOboHeader(doc);
		initOboBody(doc);

	}

	private Header goHeader;
	private Map<String, Collection<Dbxref>> termDbxrefs = new Hashtable<>();
	private Map<String, Collection<Synonym>> termSynonyms = new Hashtable<>();
	private Map<String, Collection<Subset>> termSubset = new Hashtable<>();
	private Map<String, Collection<String>> termConsider = new Hashtable<>();
	private Map<String, Collection<Relationship>> termRelation = new Hashtable<>();
	private Collection<Term> goTerms;

	@SuppressWarnings("unused")
	private Collection<Typedef> goTypes;

	public Header getGoHeader() {
		return goHeader;
	}

	public Map<String, Collection<Dbxref>> getTermDbxrefs() {
		return termDbxrefs;
	}

	public Map<String, Collection<Synonym>> getTermSynonyms() {
		return termSynonyms;
	}

	public Map<String, Collection<Subset>> getTermSubset() {
		return termSubset;
	}

	public Map<String, Collection<String>> getTermConsider() {
		return termConsider;
	}

	public Map<String, Collection<Relationship>> getTermRelation() {
		return termRelation;
	}

	public Collection<Term> getGoTerms() {
		return goTerms;
	}

	private Collection<Dbxref> addXrefs(Term tempTerm, Collection<Xref> xrefs,
			String id, boolean isFromDef) {
		LinkedList<Dbxref> list = new LinkedList<>();
		for (Xref x : xrefs) {
			Dbxref dbxref = new Dbxref();
			dbxref.setXrefDesc(x.getAnnotation());
			dbxref.setIdref(x.getIdref());// 也可能是http://
			dbxref.setFromDefinition(isFromDef);
			// dbxref.setFromSynonym(isFromSyn);
			// add to list
			list.add(dbxref);
			// add to term
			tempTerm.getXrefs().add(dbxref);
			// add tp hashtable
			addToTable(dbxref, id, termDbxrefs);
		}
		return list;
	}

	private <T1, T2> void addToTable(T2 value, T1 id,
			Map<T1, Collection<T2>> table) {
		if (table.containsKey(id)) {
			table.get(id).add(value);
		} else {
			ArrayList<T2> tempArray = new ArrayList<T2>();
			tempArray.add(value);
			table.put(id, tempArray);
		}
	}

	/**
	 * step1 init Synonym step2 init Term step3 init Term2Term
	 */
	public void initOboBody(OBODoc doc) {
		Collection<Frame> terms = doc.getTermFrames();
		goTerms = new LinkedList<Term>();
		for (Frame frame : terms) {
			Term tempTerm = new Term();
			String id = frame.getId();
			tempTerm.setId(id);
			Collection<Clause> clauses = frame.getClauses();
			for (Clause clause : clauses) {
				if (clause.getTag().equals(OboFormatTag.TAG_NAMESPACE.getTag())) {
					tempTerm.setNamesapce(clause.getValue(String.class));
				} else if (clause.getTag().equals(
						OboFormatTag.TAG_NAME.getTag())) {
					tempTerm.setName(clause.getValue(String.class));
				} else if (clause.getTag()
						.equals(OboFormatTag.TAG_DEF.getTag())) {
					tempTerm.setDef(clause.getValue(String.class));
					Collection<Xref> xrefs = clause.getXrefs();
					// include add to tempTerm
					addXrefs(tempTerm, xrefs, id, true);
				} else if (clause.getTag().equals(
						OboFormatTag.TAG_SYNONYM.getTag())) {
					Synonym syn = new Synonym();
					Collection<Object> values = clause.getValues();
					syn.setName(clause.getValue(String.class));
					syn.setType(clause.getValue2(String.class));
					if (values.size() == 3) {
						Iterator<Object> ite = values.iterator();
						Object value = null;
						while (ite.hasNext()) {
							value = ite.next();
						}
						syn.setCategory((String) value);
					}
					// 忽略掉同义词的dbxref
					// Collection<Xref> xrefs = clause.getXrefs();
					// Collection<Dbxref> dbxrefs = addXrefs(tempTerm, xrefs,
					// id, false, true);
					// syn.setXrefs(dbxrefs);
					tempTerm.getSynonyms().add(syn);
					// add to table
					addToTable(syn, id, termSynonyms);
				} else if (clause.getTag().equals(
						OboFormatTag.TAG_ALT_ID.getTag())) {
					// alt id
					Synonym syn = new Synonym();
					syn.setType(Synonym.Type.ALT_ID);
					syn.setAccSynonym(clause.getValue(String.class));
					tempTerm.getSynonyms().add(syn);
					// add to table
					addToTable(syn, id, termSynonyms);
				} else if (clause.getTag().equals(
						OboFormatTag.TAG_SUBSET.getTag())) {
					// subset
					String setName = clause.getValue(String.class);
					Subset subset = new Subset();
					subset.setName(setName);
					// ignore the subset disc
					tempTerm.getSubsets().add(subset);
					// add to table
					addToTable(subset, id, termSubset);
				} else if (clause.getTag().equals(OboFormatTag.TAG_COMMENT)) {
					// comment
					tempTerm.setComment(clause.getValue(String.class));
				} else if (clause.getTag().equals(
						OboFormatTag.TAG_IS_OBSELETE.getTag())) {
					// obselete
					tempTerm.setIs_obsolete(clause.getValue(Boolean.class));
				} else if (clause.getTag().equals(
						OboFormatTag.TAG_CONSIDER.getTag())) {
					// consider
					String con = clause.getValue(String.class);
					tempTerm.getConsiders().add(con);
					// add to table
					addToTable(con, id, termConsider);
				} else if (clause.getTag().equals(
						OboFormatTag.TAG_RELATIONSHIP.getTag())) {
					// realtion
					Relationship rel = new Relationship();
					rel.setRelation(clause.getValue(String.class));
					rel.setTermID(clause.getValue2(String.class));
					tempTerm.getRelations().add(rel);
					// add to table
					addToTable(rel, id, termRelation);
				} else if (clause.getTag().equals(
						OboFormatTag.TAG_IS_A.getTag())) {
					// is_a
					Relationship rel = new Relationship();
					rel.setRelation(RELATION.IS_A);
					rel.setTermID(clause.getValue(String.class));
					tempTerm.getRelations().add(rel);
					// add to table
					addToTable(rel, id, termRelation);
				} else if (clause.getTag().equals(
						OboFormatTag.TAG_XREF.getTag())) {
					Collection<Object> values = clause.getValues();
					for (Object v : values) {
						if (v instanceof Xref) {
							Xref tv = (Xref) v;
							Dbxref xref = new Dbxref();
							xref.setFromDefinition(false);
							xref.setIdref(tv.getIdref());
							xref.setXrefDesc(tv.getAnnotation());
							addToTable(xref, id, termDbxrefs);
						}
					}
				} else if (clause.getTag().equals(
						OboFormatTag.TAG_PROPERTY_VALUE.getTag())) {

				}

			}
			goTerms.add(tempTerm);
		}

	}

	public void initOboHeader(OBODoc doc) {
		Header header = new Header();
		Frame hframe = doc.getHeaderFrame();
		// formatVersion,dataVersion,date, subsets, imports, sysnonym_typedef
		header.setFormatVersion(hframe.getTagValue(
				OboFormatTag.TAG_FORMAT_VERSION, String.class));
		header.setDataVersion(hframe.getTagValue(OboFormatTag.TAG_DATA_VERSION,
				String.class));
		header.setDate((Date) hframe.getTagValue(OboFormatTag.TAG_DATE));
		header.setImports(hframe.getTagValues(OboFormatTag.TAG_IMPORT,
				String.class));
		Collection<Clause> subsets = hframe
				.getClauses(OboFormatTag.TAG_SUBSETDEF);
		ArrayList<Subset> subs = new ArrayList<Subset>();
		for (Clause c : subsets) {
			Subset set = new Subset();
			set.setName(c.getValue(String.class));
			set.setDesc(c.getValue2(String.class));
			subs.add(set);
		}
		header.setSubsets(subs);

		Collection<Clause> rawSyn = hframe
				.getClauses(OboFormatTag.TAG_SYNONYMTYPEDEF);
		ArrayList<SynonymCategory> subsArray = new ArrayList<SynonymCategory>();
		for (Clause c : rawSyn) {
			SynonymCategory set = new SynonymCategory();
			set.setName(c.getValue(String.class));
			set.setDesc(c.getValue2(String.class));
			subsArray.add(set);
		}
		header.setSynonymCategoties(subsArray);
		goHeader = header;
	}

}
